#include <sql.h>
#include <sqlext.h>
#include <stdio.h>
#include "snmprdbms.h"
#include "snmprdbms_odbc.h"

static SQLHENV h_env;
static SQLHDBC h_dbc;

//rewrite debug messages with DEBUG_ODBC_TOKEN prefix
#define debug odbc_debug
static void odbc_debug(const char *msg,...) {
  va_list argp;
  char f_msg[DEBUG_MSG_MAX_LEN];
  char token[sizeof(DEBUG_TOKEN) + sizeof(DEBUG_ODBC_TOKEN)];

  va_start(argp, msg);
  vsprintf(f_msg, msg, argp);
  sprintf(token, "%s:%s", DEBUG_TOKEN, DEBUG_ODBC_TOKEN);
  DEBUGMSGTL((token, f_msg));
  va_end(argp);
}

//print debug message from ODBC
void odbc_extract_error(char *fn,
                    SQLHANDLE handle,
                    SQLSMALLINT type) {
 
     SQLINTEGER   i = 0;
     SQLINTEGER   native;
     SQLCHAR      state[ 7 ];
     SQLCHAR      text[256];
     SQLSMALLINT  len;
     SQLRETURN    ret;
 
     do {
       ret = SQLGetDiagRec(type, handle, ++i, state, &native, text,sizeof(text), &len );
       if (SQL_SUCCEEDED(ret))
         debug("%s:%ld:%ld:%s\n", state, i, native, text);
     } while( ret == SQL_SUCCESS );;
 
 }

void odbc_disconnect() {
    SQLFreeHandle(SQL_HANDLE_DBC, h_dbc);
    SQLFreeHandle(SQL_HANDLE_ENV, h_env);
}

int odbc_connect(const char *cstring) {

  SQLRETURN ret; /* ODBC API return status */
  SQLCHAR outstr[1024];
  SQLSMALLINT outstrlen;

  /* Allocate an environment handle */
  SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &h_env);
  /* We want ODBC 3 support */
  SQLSetEnvAttr(h_env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
  /* Allocate a connection handle */
  SQLAllocHandle(SQL_HANDLE_DBC, h_env, &h_dbc);
  /* Connect to the DSN mydsn */
  ret = SQLDriverConnect(h_dbc, NULL, (SQLCHAR *) cstring, SQL_NTS,
					    (SQLCHAR *) outstr, (SQLSMALLINT) sizeof(outstr), &outstrlen,
                         SQL_DRIVER_NOPROMPT);

   if ( SQL_SUCCEEDED(ret) ) {

     debug("Connected to  \"%s\"\n", outstr);
     if ( ret == SQL_SUCCESS_WITH_INFO ) {
       odbc_extract_error("SQLDriverConnect", h_dbc, SQL_HANDLE_DBC);
     }

   } else {

     debug("Error while connect with connection string \"%s\"\n", cstring);
     odbc_extract_error("SQLDriverConnect", h_dbc, SQL_HANDLE_DBC);
     odbc_disconnect();
     return SNMPRDBMS_ERROR;

   }

   return SNMPRDBMS_SUCCESS;

}

//return NULL if fail and print debug message
odbcdatatable *odbc_execute(const char *query) {
	SQLHSTMT h_stmt;
	SQLSMALLINT columns;
	SQLRETURN ret;
	odbcdatatable *tablerow, *tablerow_first = NULL;

	if (query == NULL)
		return NULL;

	debug("Executing query: %s\n", query);

	SQLAllocHandle(SQL_HANDLE_STMT, h_dbc, &h_stmt);

	if (SQLExecDirect(h_stmt, (SQLCHAR *) query, SQL_NTS) != SQL_SUCCESS) {
		debug("Error while executing query: \"%s\"\n", query);
		odbc_extract_error("SQLExecDirect", h_stmt, SQL_HANDLE_STMT);
		return NULL;
	}

	SQLNumResultCols(h_stmt, &columns);

	if (columns <= 0 ) {
		debug("Query result is empty\n");
		return NULL;
	}

	debug("Query result:\n");
	while (SQL_SUCCEEDED(ret = SQLFetch(h_stmt))) {
		SQLLEN i;

		if (tablerow_first == NULL) {
			tablerow_first = malloc(sizeof(odbcdatatable));
			tablerow = tablerow_first;
		} else {
			tablerow->next_row = malloc(sizeof(odbcdatatable));
			tablerow = tablerow->next_row;
		}

		tablerow->next_row = NULL;
		tablerow->fields = calloc(columns, sizeof(odbcdatafield));

		for (i = 1; i <= columns; i++) {
			SQLINTEGER indicator[2];

			ret = SQLGetData(h_stmt, i, SQL_C_CHAR, tablerow->fields[i].data, MAX_ODBC_BUFFER_LENGTH, (SQLLEN *) &indicator[0]);
			ret = SQLDescribeCol(h_stmt, i, tablerow->fields[i].name,
								MAX_ODBC_BUFFER_LENGTH, (SQLSMALLINT *) &indicator[1],
								NULL, NULL, NULL, NULL);
			debug(" \t%s = %s\n", (indicator[0] == SQL_NULL_DATA) ? "NULL" : (char *) (tablerow->fields[i].name),
					(indicator[1] == SQL_NULL_DATA) ? "NULL" : (char *) (tablerow->fields[i].data));

		}
	}

	SQLFreeHandle(SQL_HANDLE_STMT, h_stmt);
	return tablerow_first;
}

odbcdatatable* odbc_append_table(odbcdatatable *dest, odbcdatatable *src) {
	odbcdatatable* tmp;

	if (src == NULL)
		return dest;

	if (dest == NULL)
			return src;
	else {
		//get last row
		tmp = dest;
		while (tmp != NULL)
			tmp = tmp->next_row;
		tmp->next_row = src; //append table
	}
	return dest;
}

void odbc_free_table(odbcdatatable* table) {
	odbcdatatable *tmp;

	while (table != NULL) {
		tmp = table;
		table = table->next_row;
		FREE_AND_NULL(tmp->fields);
		FREE_AND_NULL(tmp);
	}
}

//cutting table to one row
void odbc_cut_table(odbcdatatable *src) {
	int first = 0;
	odbcdatatable *tmp = NULL;

	while (src != NULL) {
		tmp = src;
		src = src->next_row;
		if (first == 0)
			first++;
		else {
			odbc_free_table(tmp);
		}
	}

	tmp->next_row == NULL;

}

//set data for named field for all rows in table
void odbc_set_field(const odbcdatatable* table, const SQLCHAR *fieldname, SQLCHAR *data) {
	int i;

	while (table != NULL) {
		for (i = 0; i < sizeof(table->fields); i++) {
			if (strcmp(table->fields[i].name, fieldname) == 0) {
				int x = 0;
				while (*data != '\0')
					table->fields[i].data[x++] = *data++;
			}
		}
		table = table->next_row;
	}
}

//return datafield or NULL
SQLCHAR* odbc_get_named_field(const SQLCHAR *fieldname, odbcdatatable* table) {
	size_t name_len;
	odbcdatatable *tmp_table;
	int i;

	name_len = strlen(fieldname);
	if (name_len > (MAX_ODBC_BUFFER_LENGTH - 1) || name_len <= 0)
		return NULL;

	for (i = 0; i < sizeof(table->fields); i++) {
		if (strcmp(table->fields[i].name, fieldname) == 0)
			return table->fields[i].data;
	}

	return NULL;
}
